package ace.cmp.spring.graphql.core.configurer;

import graphql.language.*;
import graphql.schema.idl.TypeDefinitionRegistry;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.graphql.execution.TypeDefinitionConfigurer;

/**
 * @author caspar
 * @date 2023/8/24 15:32 支持特性：
 * 1、如果不存在，则自动添加graphql schema文件的分页类型定义。分页对象{@link org.springframework.data.domain.Page }
 * 2、类型命名规则：xxxPage,xxx为对象名称
 */
public class GraphqlPageTypeDefinitionConfigurer implements TypeDefinitionConfigurer {

  private static final TypeName INTEGER_TYPE = new TypeName("Integer");
  private static final TypeName LONG_TYPE = new TypeName("Long");
  private static final String PAGE_FIELD_TOTAL_PAGES_STRING = "totalPages";
  private static final Type PAGE_FIELD_TOTAL_PAGES_TYPE = new NonNullType(INTEGER_TYPE);
  private static final String PAGE_FIELD_TOTAL_ELEMENTS_STRING = "totalElements";
  private static final Type PAGE_FIELD_TOTAL_ELEMENTS_TYPE = new NonNullType(LONG_TYPE);
  private static final String PAGE_FIELD_NUMBER_STRING = "number";
  private static final Type PAGE_FIELD_NUMBER_TYPE = new NonNullType(INTEGER_TYPE);
  private static final String PAGE_FIELD_SIZE_STRING = "size";
  private static final Type PAGE_FIELD_SIZE_TYPE = new NonNullType(INTEGER_TYPE);
  private static final String END_WITH_PAGE_STRING = "PageInput";
  private static final String PAGE_FIELD_CONTENT_STRING = "content";

  @Override
  public void configure(TypeDefinitionRegistry registry) {

    Set<String> typeNames = findConnectionTypeNames(registry);

    if (!typeNames.isEmpty()) {

      typeNames.forEach(
          typeName -> {
            String pageTypeName = typeName + END_WITH_PAGE_STRING;

            registry.add(
                ObjectTypeDefinition.newObjectTypeDefinition()
                    .name(pageTypeName)
                    .fieldDefinition(
                        initFieldDefinition(
                            PAGE_FIELD_CONTENT_STRING,
                            new NonNullType(new ListType(new TypeName(typeName))),
                            "数据内容"))
                    .fieldDefinition(
                        initFieldDefinition(
                            PAGE_FIELD_TOTAL_PAGES_STRING, PAGE_FIELD_TOTAL_PAGES_TYPE, "总页数"))
                    .fieldDefinition(
                        initFieldDefinition(
                            PAGE_FIELD_TOTAL_ELEMENTS_STRING,
                            PAGE_FIELD_TOTAL_ELEMENTS_TYPE,
                            "总数量"))
                    .fieldDefinition(
                        initFieldDefinition(
                            PAGE_FIELD_NUMBER_STRING, PAGE_FIELD_NUMBER_TYPE, "当前页码"))
                    .fieldDefinition(
                        initFieldDefinition(PAGE_FIELD_SIZE_STRING, PAGE_FIELD_SIZE_TYPE, "页大小"))
                    .build());
          });
    }
  }

  private static Set<String> findConnectionTypeNames(TypeDefinitionRegistry registry) {
    return registry.types().values().stream()
        .filter(definition -> definition instanceof ImplementingTypeDefinition)
        .flatMap(
            definition -> {
              ImplementingTypeDefinition<?> typeDefinition =
                  (ImplementingTypeDefinition<?>) definition;
              return typeDefinition.getFieldDefinitions().stream()
                  .map(
                      fieldDefinition -> {
                        Type<?> type = fieldDefinition.getType();
                        return (type instanceof NonNullType
                            ? ((NonNullType) type).getType()
                            : type);
                      })
                  .filter(type -> type instanceof TypeName)
                  .map(type -> ((TypeName) type).getName())
                  .filter(name -> name.endsWith(END_WITH_PAGE_STRING))
                  .filter(name -> registry.getType(name).isEmpty())
                  .map(name -> name.substring(0, name.length() - END_WITH_PAGE_STRING.length()));
            })
        .collect(Collectors.toCollection(LinkedHashSet::new));
  }

  private FieldDefinition initFieldDefinition(String name, Type<?> returnType, String comment) {
    FieldDefinition.Builder builder = FieldDefinition.newFieldDefinition();
    builder.name(name).type(returnType);
    if (comment != null && comment.length() > 0) {
      builder.comments(Arrays.asList(new Comment(comment, SourceLocation.EMPTY)));
    }
    return builder.build();
  }
}
